﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DwarfParser
{
    class DwarfParser : BaseDwarfParser
    {
        public DwarfParser(DwarfScanner scanner)
        {
            this.scanner = scanner;
        }
     
        public void Parse()
        {
            tree = new AsTree();

            ParseProgram();
        }

        private void ParseProgram()
        {
            AstMainClass mainClass = ParseMainClass();
            tree.SetRootNode(mainClass);            
        }

        private AstMainClass ParseMainClass()
        {
            ParseToken(TokenType.CLASS);
            AstId className = ParseId();
            ParseToken(TokenType.LEFT_BRACE);
            AstEntryPointMainFunctionDecl mainMethodDeclaration = ParseMainMethodDeclaration();
            ParseToken(TokenType.RIGHT_BRACE);
            return new AstMainClass(className, mainMethodDeclaration);
        }

        private AstId ParseId()
        {
            ParseToken(TokenType.ID);
            return new AstId(currentToken.Attribute);
        }

        private AstEntryPointMainFunctionDecl ParseMainMethodDeclaration()
        {
            ParseToken(TokenType.PUBLIC);
            AstAccessModifier accessModifier = new AstAccessModifier();
            ParseToken(TokenType.STATIC);
            AstStaticModifier staticModifier = new AstStaticModifier();
            ParseToken(TokenType.TYPE_DECL);
            Check(currentToken.Attribute == "void"); // TODO: remove const
            AstType type = new AstType(AstType.Type.VOID);
            ParseToken(TokenType.MAIN);
            AstId functionName = new AstId("main");  // TODO: remove const
            AstFunctionArgumentsList argumentsList = ParseFunctionArgumentList();
            AstStatements stmts = ParseStatements();

            return new AstEntryPointMainFunctionDecl(
                    accessModifier, 
                    staticModifier, 
                    type, 
                    functionName, 
                    argumentsList,
                    stmts
                );
        }

        private AstFunctionArgumentsList ParseFunctionArgumentList()
        {
            ParseToken(TokenType.LEFT_PAREN);

            List<AstArgumentDecl> list = ParseArgumentDeclarations();
            
            ParseToken(TokenType.RIGHT_PAREN);
            return new AstFunctionArgumentsList(list);
        }

        private List<AstArgumentDecl> ParseArgumentDeclarations()
        {
            List<AstArgumentDecl> list = new List<AstArgumentDecl>();
            if (GetForwardToken().Type == TokenType.RIGHT_PAREN)
            {
                return list;
            }

            list.Add(ParseArgumentDecl()); // TODO: use DO-WHILE or recursion instead ?

            while (GetForwardToken().Type == TokenType.COMMA)
            {
                ParseToken(TokenType.COMMA);
                list.Add(ParseArgumentDecl());
            }

            return list;
        }

        private AstArgumentDecl ParseArgumentDecl()
        {
            AstType type = ParseType();
            AstId id = ParseId();
            return new AstArgumentDecl(type, id);
        }

        private AstType ParseType()
        {
            ParseToken(TokenType.TYPE_DECL);
            AstType.Type baseType;
           
            baseType = MyMaps.StringToAstType(currentToken.Attribute);
            
            if (GetForwardToken().Type == TokenType.LEFT_BRACKET)
            {
                GetNextToken();
                ParseToken(TokenType.RIGHT_BRACKET);

                switch (baseType)
                {
                    case AstType.Type.INT:
                        baseType = AstType.Type.INT_ARR;
                        break;
                    case AstType.Type.STRING:
                        baseType = AstType.Type.STRING_ARR;
                        break;
                    default:
                        TriggerError("Only int[] and String[] arrays are supported.");
                        break;
                }
            }

            return new AstType(baseType);
        }

        private AstStatements ParseStatements()
        {
            ParseToken(TokenType.LEFT_BRACE);

            List<AstStatement> list = new List<AstStatement>();

            TokenType type = GetForwardToken().Type;
            while (type != TokenType.RIGHT_BRACE && !HasErrors)
            {
                list.Add(ParseStatement());
                type = GetForwardToken().Type;
            }

            ParseToken(TokenType.RIGHT_BRACE);

            return new AstStatements(list);
        }

        private AstStatement ParseStatement()
        {
            switch (GetForwardToken().Type)
            {                
                case TokenType.IF:         
                    return ParseIfStatement();
                case TokenType.WHILE:      
                    return ParseWhileStatement();                
                case TokenType.ID:
                case TokenType.INTEGER: 
                    return ParseSimpleStatement();
                case TokenType.LEFT_BRACE: 
                    return ParseStatements();
                default:
                    TriggerError(" Expecting LEFT_BRACE, IF, WHILE, INTEGER or ID tokens.");
                    return new AstErroneousStatement();
            }
        }

        private AstWhileStatement ParseWhileStatement()
        {
            ParseToken(TokenType.WHILE);
            ParseToken(TokenType.LEFT_PAREN);
            AstExpression cond = ParseExpression();
            ParseToken(TokenType.RIGHT_PAREN);
            return new AstWhileStatement(cond, ParseStatements());
        }
        
        private AstIfStatement ParseIfStatement()
        {
            ParseToken(TokenType.IF);
            ParseToken(TokenType.LEFT_PAREN);
            AstExpression cond = ParseExpression();
            ParseToken(TokenType.RIGHT_PAREN);
            AstStatements thenStatements = ParseStatements();
            AstStatements elseOptStatements = null;
            if (GetForwardToken().Type == TokenType.ELSE)
            {
                ParseToken(TokenType.ELSE);
                elseOptStatements = ParseStatements();
            }

            return new AstIfStatement(cond, thenStatements, elseOptStatements);
        }
        
        private AstExpression ParseExpression()
        {
            // либо ID, либо IAstClassField, либо integer, 
            // либо выражения с +, -, *, или /

            AstExpression expr = ParseExpressionPart();
            
            bool twoOperands = false;
            Token t = GetForwardToken();
            switch (t.Type)
            {
                case TokenType.PLUS:
                case TokenType.MINUS:
                case TokenType.MULTIPLICATION:
                case TokenType.DIVISION:
                case TokenType.GT:
                case TokenType.LT:
                case TokenType.AND:
                case TokenType.OR:
                    twoOperands = true;
                    break;
            }

            if (twoOperands)
            {
                t = GetNextToken();
                AstExpression secodOperand = ParseExpression();
                expr = CreateExpressionFromParts(t, expr, secodOperand);
            }

            return expr;
        }

        private AstExpression ParseExpressionPart()
        {
            TokenType first = GetForwardToken().Type;
            AstExpression expr;
            if (first == TokenType.ID)
            {
                AstId id = ParseId();
                if (GetForwardToken().Type == TokenType.DOT)
                {
                    expr = new AstDotExpression(id, ParseId());

                    if (GetForwardToken().Type == TokenType.LEFT_PAREN)
                    {
                        ParseToken(TokenType.LEFT_PAREN);
                        expr = new AstFunctionCallWithReturnVal(expr, ParseArgumentList());
                        ParseToken(TokenType.RIGHT_PAREN);
                    }
                }
                else
                {
                    expr = id;
                }
            }
            else if (first == TokenType.INTEGER)
            {
                ParseToken(TokenType.INTEGER);
                expr = new AstInteger(currentToken.Attribute);
            }
            else
            {
                TriggerError("error expression.");
                expr = new AstErroneousExpression(GetNextToken().ToString());
            }

            return expr;
        }

        private AstExpression CreateExpressionFromParts(Token t, AstExpression left, AstExpression right)
        {
            switch (t.Type)
            {
                case TokenType.PLUS:     return new AstAddExpression(left, right);
                case TokenType.MINUS:    return new AstMinusExpression(left, right);
                case TokenType.MULTIPLICATION: return new AstMultiplicationExpression(left, right);
                case TokenType.DIVISION: return new AstDivisionExpression(left, right);
                case TokenType.GT:       return new AstLogicalExpression(TokenType.GT, left, right);
                case TokenType.LT:       return new AstLogicalExpression(TokenType.LT, left, right);
                case TokenType.AND:      return new AstLogicalExpression(TokenType.AND, left, right);
                case TokenType.OR:       return new AstLogicalExpression(TokenType.OR, left, right);
                default:                 return new AstErroneousExpression(t.ToString());
            }
        }

        private AstStatement ParseSimpleStatement()
        {
            AstStatement result;            
            if (GetForwardToken().Type == TokenType.TYPE_DECL)
            {
                result = ParseArgumentDecl();
            }
            else
            {
                AstId id = ParseId();
                AstExpression obj;
                if (GetForwardToken().Type == TokenType.DOT)
                {
                    ParseToken(TokenType.DOT);
                    obj = new AstDotExpression(id, ParseId());
                }
                else
                {
                    obj = id;
                }

                if (GetForwardToken().Type == TokenType.LEFT_PAREN)
                {
                    result = new AstFunctionCall(obj, ParseArgumentList());
                }
                else
                {
                    ParseToken(TokenType.ASSIGMENT);
                    result = new AstAssigmentExpression(obj, ParseExpression());
                }
            }
            ParseToken(TokenType.SEMICOLON);

            return result;
        }

        private AstArgumentsList ParseArgumentList()
        {
            List<AstExpression> list = new List<AstExpression>();

            ParseToken(TokenType.LEFT_PAREN);
            if (GetForwardToken().Type != TokenType.RIGHT_PAREN)
            {
                list.Add(ParseExpression());

                if (GetForwardToken().Type == TokenType.COMMA)                
                {
                    while (GetForwardToken().Type != TokenType.RIGHT_PAREN)
                    {
                        ParseToken(TokenType.COMMA);
                        list.Add(ParseExpression());
                    }
                }
            }
            ParseToken(TokenType.RIGHT_PAREN);

            return new AstArgumentsList(list);
        }
    }
}
